package xj.graph2d.shapes.uml;

import static xj.graph2d.DrawShapeConstants.COMPARTMENT_ROLE_BASE;
import static xj.graph2d.DrawShapeConstants.PointEastEdge;
import static xj.graph2d.DrawShapeConstants.cpadx;
import static xj.graph2d.DrawShapeConstants.cpady;
import static xj.graph2d.DrawShapeUtil.getShapeBounds;
import static xj.graph2d.DrawShapeUtil.moveShapes;

import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.awt.geom.Rectangle2D;
import javax.swing.SwingConstants;

import xj.graph2d.BoxGroup;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.RectangleShape;
import xj.graph2d.Label;
import xj.graph2d.shapes.BoxLabel;
import xj.graph2d.shapes.CompartmentalRectangleShape;
import xj.graph2d.shapes.CompartmentalRoundRectangleShape;
import xj.graph2d.shapes.HorizontalCompartmentsShape;
import xj.graph2d.shapes.RectangularShape;
import xj.graph2d.shapes.RoundRectangleShape;
import xj.graph2d.shapes.ShapeConstants;
import xj.graph2d.shapes.TitledShape;
import xj.graph2d.shapes.block.BlockShape;
import xj.meta.ModelElement;
import xj.model.uml.State;

public class CompoundStateShape 
  extends TitledShape 
  implements StateShape {

  public CompoundStateShape() {
  }

  public CompoundStateShape(float x, float y, float w, float h) {
    this(x, y, w, h, false, false, false);
  }

  public CompoundStateShape(float x, float y, float w, float h,
			    boolean createComp) {
    this(x, y, w, h, false, false, createComp);
  }

  public CompoundStateShape(float x, float y, float w, float h,
			    boolean showAction, boolean createComp) {
    this(x, y, w, h, showAction, false, createComp);
  }

  public CompoundStateShape(float x, float y, float w, float h,
			    boolean showAction, boolean machine, 
			    boolean createComp) {
    super((machine ? new CompartmentalRectangleShape(x, y, w, h, showAction ? new float[] { 0.2f } : null)
	           : new CompartmentalRoundRectangleShape(x, y, w, h - TITLE_HEIGHT, 
							  showAction ? new float[] { 0.2f } : null)),
	  (machine ? new RectangularShape(ShapeConstants.PENTAGON2_SHAPE, ShapeConstants.SOUTH, 
					  x, y, w * 0.3f, TITLE_HEIGHT)
	           : new RectangleShape(x, y, w * 0.3f, TITLE_HEIGHT)),
	  (machine ? TitledShape.UNDER_TOP_LEFT : TitledShape.ABOVE_TOP_LEFT), 
	  x, y, x + w, y + h, createComp);
    HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
    body.setNumberOfHorizontalCompartmentsFixed(true);
    body.setScaleTopCompartment(false);
    actionCompartmentShown = showAction;
  }

  public CompoundStateShape(HorizontalCompartmentsShape mainShape,
			    DrawShape titleShape, 
			    float x, float y, float w, float h) {
    this(mainShape, titleShape, TitledShape.ABOVE_TOP_LEFT, x, y, w, h, false);
  }

  public CompoundStateShape(HorizontalCompartmentsShape mainShape,
			    DrawShape titleShape, 
			    float x, float y, float w, float h,
			    boolean createComp) {
    this(mainShape, titleShape, TitledShape.ABOVE_TOP_LEFT, x, y, w, h, createComp);
  }

  public CompoundStateShape(HorizontalCompartmentsShape mainShape,
			    DrawShape titleShape, int titlePosition, 
			    float x, float y, float w, float h, 
			    boolean createComp) {
    super(mainShape, titleShape, titlePosition, x, y, x + w, y + h, createComp);
    if (mainShape != null) {
      mainShape.setNumberOfHorizontalCompartmentsFixed(true);
      mainShape.setScaleTopCompartment(false);
      actionCompartmentShown = (mainShape.getNumberOfHorizontalCompartments() > 1);
    }
  }

  @Override
  public DrawShape makeInstance() 
    throws CloneNotSupportedException {
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    float x = Math.min(x1, x2);
    float y = Math.min(y1, y2);
    float w = Math.abs(x2 - x1);
    float h = Math.abs(y2 - y1);
    return new CompoundStateShape((HorizontalCompartmentsShape) 
				    ((mainShape != null) ? mainShape.clone() : mainShape), 
				  (RectangleShape) 
				    ((titleShape != null) ? titleShape.clone() : titleShape), 
				  titlePosition, x, y, w, h, comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s)
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof CompoundStateShape) {
      CompoundStateShape ss = (CompoundStateShape) s;
      ss.actionCompartmentShown = actionCompartmentShown;
    }
  }

  @Override
  protected void cloneSubshapes(DrawShapeBase s, boolean deepClone) 
    throws CloneNotSupportedException {
    super.cloneSubshapes(s, deepClone);
    if (s instanceof CompoundStateShape) {
      CompoundStateShape ss = (CompoundStateShape) s;
      ss.actionBox = ss.findInRole(ACTION_ROLE);
      if (ss.actionBox != null) { 
	ss.actionBox.setVisible(ss.actionCompartmentShown);
      }
    }
  }

  @Override
  public String getNodeDisplayName() { 
    return getStateName();
  }

  public String getStateName() {
    return UMLUtil.getName(this);
  }

  public void setStateName(String cname) {
    UMLUtil.setName(this, cname);
    //doLayout();
  }

  public String getStateStereotypes() {
    return UMLUtil.getStereotypes(this);
  }

  public void setStateStereotypes(String st) {
    UMLUtil.setStereotypes(this, st);
  }

  // shape --> model
  public void getActions(State state) {
    if (state != null && 
	actionCompartmentShown) {
      DrawShape box = getActionBox();
      UMLStateUtil.getActions(state, box);
    }
  }

  // model --> shape
  public void setActions(State state) {
    if (state != null && 
	actionCompartmentShown) {
      DrawShape newbox = UMLStateUtil.setActions(state, actionBox);
      setActionBox(newbox);	
      actionBox = newbox;
      fitCompartments();
      doLayout();
    }
  }

  public float getMinimumTitleWidth() { 
    float tw0 = titleShape.getShapeWidth();
    BoxLabel header = (BoxLabel) getLabelShape(DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
    if (header != null) { 
      float tw = header.getShapeWidth() + 2 * cpady;
      if (tw > tw0) {
	tw0 = tw; 
      }      
    }
    return tw0;
  }

  /*
  public void adjustTitleSize() {   
    //System.out.println("CompoundStateShape.adjustTitleSize()");

    float height = getShapeHeight();
    float width = getShapeWidth();
    float th0, tw0, h, w;
    float th = 2 * cpady, tw = 2 * cpady;

    th0 = titleShape.getShapeHeight();
    tw0 = titleShape.getShapeWidth();
    BoxLabel header = (BoxLabel) getLabelShape(DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
    if (header != null) { 
      th = header.getShapeHeight() + 2 * cpady;
      tw = header.getShapeWidth() + 2 * cpady;
    }
    h = Math.max(th0, th); 
    w = Math.max(tw0, tw); 
    titleShape.moveEnd2((w - tw0), (h - th0));

    if (false) {
      System.out.println("CompoundStateShape.adjustTitleSize() " + header.getClass().getName() + " " + 
			 header.getText() + "  " + tw0 + "  " + th0 + "  " + tw + "  " + th);
    }

    float offset = 0;

    if (mainShape instanceof RoundRectangleShape) {
      RoundRectangleShape r = (RoundRectangleShape) mainShape;
      offset = r.getArcWidth();
    }
    if (w > (width - offset)) {
      mainShape.moveEnd2((w + offset - width), 0);
    }

    positionTitle();
    doLayout();
  }
  */

  @Override
  public void positionSubNodes() {
    positionSubNodes(true);
  }

  public void positionSubNodes(boolean fit) {
    if (fit) { 
      float left = (float) Math.min(p1.getX(), p2.getX());
      float top = (float) Math.min(p1.getY(), p2.getY());
      float width = (float) Math.abs(p1.getX() - p2.getX());
      float height = (float) Math.abs(p1.getY() - p2.getY());
      
      Label header = getLabelShape(DrawShapeConstants.ROLE_NODE_TITLE_LABEL); 
      float w0 = 0, h0 = 0;      
      if (header != null) { 
	w0 = header.getShapeWidth() + 2 * cpady;
	h0 = header.getShapeHeight() + 2 * cpady;
      } else { 
	w0 = 2 * cpady;
	h0 = 2 * cpady;
      }

      float titleHeight = titleShape.getShapeHeight(); 
      float titleWidth = titleShape.getShapeWidth(); 
      if (w0 > titleWidth) { 
	titleShape.moveEdge(PointEastEdge, (w0 - titleWidth), 0);
	titleWidth = w0;
      }

      HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
      float hbody = body.getShapeHeight();
      float actionHeight = 0;
      float dx = 0;
      float dy = 0; 

      // position action box 
      if (actionBox != null && actionBox.isVisible()) { 
	actionHeight = actionBox.getShapeHeight() + 2 * cpady;
      }

      // position substates 
      List<GraphNodeBase> substates = findSubstates();
      Rectangle2D subbounds = getShapeBounds(substates);
      if (substates.size() > 0 && 
	  subbounds != null) { 
	float y = top + titleHeight + actionHeight + MARGIN_GAP; 
	float x = left + MARGIN_GAP;

	if (x > subbounds.getX()) { 
	  dx = x - (float) subbounds.getX();
	}
	if (y > (float) subbounds.getY()) { 
	  dy = y - (float) subbounds.getY();
	}
	if (dx > 0 || dy > 0) {
	  moveShapes(substates, dx, dy);
	}

	if (debug) 
	  System.out.println("CompoundStateShape.positionSubNodes() [" + left + "," + top + "," + 
			     width + "," + height + "] dx=" + dx + " dy=" + dy + " subbouds=" + subbounds);
			   
	float x2 = (float) subbounds.getX() + (float) subbounds.getWidth() + dx + MARGIN_GAP; 
	float y2 = (float) subbounds.getY() + (float) subbounds.getHeight() + dy + MARGIN_GAP; 
	dx = dy = 0;
	if (x2 > (left + width)) {
	  dx = (x2 - (left + width)); 
	}
	if (y2 > (top + height)) { 
	  dy = (y2 - (top + height));
	}
	
	if (debug) 
	  System.out.println("CompoundStateShape.positionSubNodes() x2=" + x2 + " y2=" + y2 +  
			     " dx=" + dx + " dy=" + dy);

	if (dx > 0 || dy > 0) { 
	  setEnds(left, top, left + width + dx, top + height + dy);
	}	
      }

      if (actionHeight > 0) { 
	float[] dividers = body.getHorizontalDividers();
	if (dividers != null && dividers.length > 0) {
	  int n = dividers.length;
	  float[] newDividers = new float[n];
	  float stateHeight = height + dy - titleHeight - actionHeight; 
	  height = (actionHeight + stateHeight);
	  newDividers[0] = actionHeight / height;
	  for (int i = 1; i < n; i++) {
	    newDividers[i] = (dividers[i] * hbody) / height;
	  }
	  body.setHorizontalDividers(newDividers);
	}
      }
      
    }
  }

  public List<GraphNodeBase> findSubstates() { 
    List<GraphNodeBase> result = new ArrayList<GraphNodeBase>();
    if (subshapes != null) {
      for (DrawShape s : subshapes) { 
	if (s.isVisible() && 
	    s instanceof GraphNodeBase) {
	  GraphNodeBase node = (GraphNodeBase) s;
	  int role = node.getRole();
	  if (role != DrawShapeConstants.ROLE_NODE_TITLE_LABEL &&
	      role != ACTION_ROLE && 
	      !node.isOnBoundary()) {
	    result.add(node);
	  }
	}
      }
    }
    return result; 
  }

  public boolean isActionCompartmentShown() {
    return actionCompartmentShown;
  }

  public void showActionCompartment() {
    if (!actionCompartmentShown) {
      actionCompartmentShown = true;

      // necessary to sync with model, which may have been 
      // edited when the action box is hidden
      ModelElement model = getModel();
      if (model instanceof State) {
	State state = (State) model;
	setActions(state);
      }

      HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
      float[] dividers;
      float h;
      float h0 = body.getShapeHeight();
      if (actionBox != null) {
	actionBox.setVisible(true);
	float h1 = actionBox.getShapeHeight() + 2 * cpady;
	h = (h0 + h1);
	dividers = new float[] { h1 / h };

	if (debug) System.out.println("CompoundStateShape.showActionCompartment() h1=" + h1 + " h0=" + h0 + " h=" + h);

	moveSubshapes(0, h1);
	moveEnd2(0, h1);
      } else {
	dividers = new float[] { 0.2f };
	moveSubshapes(0, h0 * 0.25f);
	moveEnd2(0, h0 * 0.25f);
      }
      body.setHorizontalDividers(dividers);

      positionActionBox();
      //fitCompartments();
      doLayout();
    }
  }

  public void hideActionCompartment() {
    actionBox = findInRole(ACTION_ROLE);
    if (actionBox != null) {
      //removeShape(actionBox);
      actionBox.setVisible(false);
    }
    
    if (mainShape instanceof HorizontalCompartmentsShape) { 
      HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
      float h = body.getShapeHeight();
      float dy = 0;
      float[] dividers = body.getHorizontalDividers();
      if (dividers != null && dividers.length > 0) {
	dy = -h * dividers[0];
      }
      body.setHorizontalDividers(null);
      moveSubshapes(0, dy);
      moveEnd2(0, dy);
      doLayout();
    }
    actionCompartmentShown = false;
  }

  public void initAction(DrawShape action) {
    actionBox = action;
    if (actionBox != null) {
      actionBox.setDisplayName("Action");
      setActionBox(action);
      actionBox.setVisible(actionCompartmentShown);
    }
    doLayout();
  }

  public boolean isModernStyle() { 
    return (actionBox != null && actionBox instanceof BlockShape);
  }

  public void changeStyle(DrawShape action) { 
    if (actionBox != null && action != null) {
      ModelElement model = getModel();
      if (model instanceof State) {
	State state = (State) model;
	initAction(action);
	setActions(state);
      }
    }
  }

  public DrawShape getActionBox() {
    if (actionCompartmentShown) { 
      return findInRole(ACTION_ROLE);
    } 
    return null;
  }

  static final int ACTION_ROLE = COMPARTMENT_ROLE_BASE;

  public void setActionBox(DrawShape action) {
    if (action != null) { 
      DrawShape s0 = findInRole(ACTION_ROLE);
      if (s0 != null && 
	  s0 != action) {
	removeShape(s0);
	s0 = null; 
      }
      if (s0 == null) { 
	action.setRole(ACTION_ROLE); 
	addShape(action); 
      }

      positionActionBox();
    }
  }

  protected void positionActionBox() { 
    if (actionBox != null && 	
	actionBox.isVisible() && 
	actionCompartmentShown &&
	mainShape instanceof CompartmentalRoundRectangleShape) {
      HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
      float h = body.getHorizontalCompartmentHeight(0);
      float x = (float) body.getEnd1().getX(); 
      float y = (float) body.getEnd1().getY(); 
      float w = body.getShapeWidth(); 

      float dx = 0, dy = 0;
      if (w > cpadx * 2) {
	dx = cpadx;
	x += dx;
	w -= dx * 2;
      }
      if (h > cpady * 2) {
	dy = cpady;
	y += dy;
	h -= dy * 2;
      }
      if (actionBox instanceof BoxGroup) {
	actionBox.doLayout();
	positionShape(actionBox, x, y, w, h, SwingConstants.TOP, SwingConstants.LEFT);
      } else { 
	actionBox.setEnds(x, y, x + w, y + h);
	actionBox.doLayout();
      }
    }      
  }

  protected void moveSubshapes(float dx, float dy) {
    if (subshapes != null) {
      int count = subshapes.size();
      for (int i = 0; i < count; i++) {
	DrawShape s = subshapes.get(i);
	int role = s.getRole();
	if (role != DrawShapeConstants.ROLE_NODE_TITLE_LABEL &&
	    role != ACTION_ROLE) {
	  s.move(dx, dy);
	}
      }
    }
  }

  public void arrangeSubshapes() {
    actionBox = findInRole(ACTION_ROLE);
  }

  @Override
  public String toString() { 
    return "CompoundStateShape " + getStateName();
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }

    attrs.put("showAction", actionCompartmentShown);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("showAction");
      actionCompartmentShown = Boolean.valueOf(s);

      if (actionBox != null) { 
	actionBox.setVisible(actionCompartmentShown);
      }

    }
  }

  
  // adjust dividers 
  public void fitCompartments(boolean keepWidth) {
    //System.out.println("CompoundStateShape.fitCompartments()");

    HorizontalCompartmentsShape body = (HorizontalCompartmentsShape) mainShape;
    float[] dividers;
    float hh = body.getShapeHeight();
    if (actionBox != null &&
	actionBox.isVisible()) { 
      float h0 = actionBox.getShapeHeight() + 2 * cpady;
      float h1 = body.getHorizontalCompartmentHeight(1); 
      float h = (h0 + h1);
      dividers = new float[] { h0 / h };
      float dy = h - hh; 
      moveSubshapes(0, dy);
      moveEnd2(0, dy);
      body.setHorizontalDividers(dividers);
      positionActionBox();
    }
    //body.fitCompartments(keepWidth);
  }

  @Override
  public void doLayout() {
    super.doLayout();
    positionActionBox();
  }

  protected boolean actionCompartmentShown;

  protected DrawShape actionBox;

  protected static final int TITLE_HEIGHT = 20;

  protected static final int MARGIN_GAP   = 20;

  protected static final int padx = 5, pady = 5;

 

}
